#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "ClassFile.h"
#include "SimpleIo.h"
#include "FieldInfo.h"
#include "ClassFileConstant.h"
#include "ConstantPool.h"
#include "MethodInfo.h"
#include "AttributeInfo.h"
#include "Log.h"

enum {
  magic_number=0xcafebabe
}; 


static const char * defaultClasses[]=
{
  "java/io/PrintStream",
  "java/lang/System"
};

#if 0
static struct { 
  const char * klass;
  const char * name_and_type; 
} defaultConstantFieldRef[]=
{
//  { "java/lang/System", "out Ljava/io/PrintStream;" }
};
#endif

#define DEFAULT_SUPER_CLASS_NAME "java/lang/Object"
#define DEFAULT_ACCESS_FLAGS     0x20

#define DEFAULT_MAIN_METHOD_NAME            "main"
#define DEFAULT_MAIN_METHOD_SIGNATURE       "([Ljava/lang/String;)V"
#define DEFAULT_MAIN_METHOD_ACCESS_FLAGS    (ACC_PUBLIC|ACC_STATIC)

ClassFile::ClassFile()
{
}

ClassFile * ClassFile::newClassFile(const char * classname)
{
  ClassFile * self;
  self=new ClassFile();

  self->m_access_flags=0;

  self->m_constantPool=new ConstantPool();
  self->m_this_class=self->m_constantPool->addClass(classname);
  self->m_super_class=self->m_constantPool->addClass(DEFAULT_SUPER_CLASS_NAME);
  self->m_major_version=49;
  self->m_minor_version=0;

  /* add 'default' classes references */
  u2 nDefaultClasses=
      sizeof(defaultClasses)/sizeof(defaultClasses[0]);
  for(u2 i=0;i<nDefaultClasses;i++) 
    self->m_constantPool->addClass(defaultClasses[i]);

  /* add 'default' constant fields references */
#if 0
  u2 nDefaultConstantFieldRef=sizeof(defaultConstantFieldRef[0])/
    sizeof(defaultConstantFieldRef[0]);
  for(u2 i=0;i<nDefaultConstantFieldRef;i++) 
    self->m_constantPool->addFieldRef(
        defaultConstantFieldRef[i].klass,
        defaultConstantFieldRef[i].name_and_type);
#endif

  return self;
}

MethodInfo * ClassFile::addDefaultMainMethod()
{
  MethodInfo * method;

  method=getMethod(DEFAULT_MAIN_METHOD_NAME,
        DEFAULT_MAIN_METHOD_SIGNATURE);
  assert(method==0);

  assert(m_constantPool);
  method= new MethodInfo(m_constantPool,
        DEFAULT_MAIN_METHOD_NAME,
        DEFAULT_MAIN_METHOD_SIGNATURE,
        DEFAULT_MAIN_METHOD_ACCESS_FLAGS);

  m_methods.push_back(method);

  return method;
}

void ClassFile::save()
{
  char buffer[1024];

  snprintf(buffer,1024,"%s.class",getClassName());

  FILE * f;

  f=fopen(buffer,"wb");
  if(!f) 
    return;

  LOG_SAVE("------------------------- saving\n");
  writeUint32(f,magic_number);
  writeUint16(f,m_minor_version);
  writeUint16(f,m_major_version);

  LOG_SAVE("at %ld saving costant pool\n",ftell(f));
  m_constantPool->save(f);

  writeUint16(f,m_access_flags);
  writeUint16(f,m_this_class);
  writeUint16(f,m_super_class);

  LOG_SAVE("At %ld saving interfaces\n",ftell(f));
  writeUint16(f,m_interfaces.size());
  for(u2 i; i<m_interfaces.size(); i++)
    writeUint16(f,m_interfaces[i]);

  LOG_SAVE("at %ld saving %d fields\n",ftell(f),(int)m_fields.size());
  writeUint16(f,m_fields.size());
  for(u2 i=0; i<m_fields.size(); i++)
    m_fields[i]->save(f);

  LOG_SAVE("at %ld saving methods\n",ftell(f));
  writeUint16(f,m_methods.size());
  for(u2 i=0; i<m_methods.size(); i++)
    m_methods[i]->save(f);

  LOG_SAVE("at %ld saving attributes\n",ftell(f));
  writeUint16(f,m_attributes.size());
  for(u2 i=0; i<m_attributes.size(); i++)
    m_attributes[i]->save(f,m_constantPool);

  LOG_SAVE("at %ld saved all class\n",ftell(f));

  fclose(f);
}

MethodInfo * ClassFile::getMethod(const char * methodName, 
        const char * signature)
{
  // TODO: should not be a linear search!
  for(u2 i=0;i<m_methods.size();i++) 
    if(strcmp(m_methods[i]->getName(),methodName)==0 &&
       strcmp(m_methods[i]->getSignature(),signature)==0)
      return m_methods[i];
  return 0;
}

const char * ClassFile::getClassName()
{
  ClassFileConstantClass * cc;

  cc=
    (ClassFileConstantClass*)(m_constantPool->getAt(m_this_class));
  
  assert(m_constantPool->isUtf8At(cc->name_index));

  return m_constantPool->getCString(cc->name_index);
}

ClassFile::ClassFile(const char * filename)
{
  FILE * f;
  unsigned int magic;

  m_constantPool=new ConstantPool();

  f=fopen(filename,"rb");

  assert(f);

  LOG_LOAD("------------------- loading\n");
  magic=readUint32(f);
  m_minor_version=readUint16(f);
  m_major_version=readUint16(f);

  LOG_LOAD("at %ld loading constant pool\n",ftell(f));
  u2 constant_pool_count;
  constant_pool_count=readUint16(f);
  u2 usedSpots;
  for(u2 i=1;i<constant_pool_count;i+=usedSpots) {
    usedSpots=m_constantPool->addFromFile(f);
  }

  m_access_flags=readUint16(f);

  m_this_class=readUint16(f);
  LOG_LOAD("this class is at %d\n",m_this_class);
  assert(m_constantPool->isClassAt(m_this_class));
  
  m_super_class=readUint16(f);
  assert(m_constantPool->isClassAt(m_super_class));

  LOG_LOAD("at %ld loading interfaces\n",ftell(f));
  u2 icount=readUint16(f);
  for(u2 i=0;i<icount;i++) {
    m_interfaces.push_back(readUint16(f));
  }

  LOG_LOAD("at %ld loading fields\n",ftell(f));
  u2 fcount=readUint16(f);
  for(u2 i=0;i<fcount;i++) {
    m_fields.push_back(new FieldInfo(m_constantPool,f));
  }

  LOG_LOAD("at %ld loading methods\n",ftell(f));
  u2 mcount=readUint16(f);
  for(u2 i=0;i<mcount;i++) {
    m_methods.push_back(new MethodInfo(m_constantPool,f));
  }

  LOG_LOAD("at %ld loading attributes\n",ftell(f));
  u2 acount=readUint16(f);
  for(u2 i=0;i<acount;i++) {
    m_attributes.push_back(AttributeInfoFactory::fromFile(m_constantPool,f));
  }
  LOG_LOAD("at %ld loaded all class\n",ftell(f));

}

void ClassFile::dump()
{
  printf("Class: ");
  m_constantPool->getAt(m_this_class)->dump(m_constantPool);
  printf("\nSuper Class: ");
  m_constantPool->getAt(m_super_class)->dump(m_constantPool);
  printf("\n");

  if(m_interfaces.size()) {
    printf("Interfaces:\n");
    dumpInterfaces();
  }
  if(m_fields.size()) {
    printf("Fields (%d):\n",(int)m_fields.size());
    dumpFields();
  }
  if(m_methods.size()) {
    printf("Methods (%d):\n",(int)m_methods.size());
    dumpMethods();
  }
  printf("Constant pool:\n");
  dumpConstantPool();
}

void ClassFile::dumpFields() 
{
  for(unsigned int i=0; i<m_fields.size(); i++) {
    printf(" -%03d ",i);
    m_fields[i]->dump(m_constantPool);
  }
}

void ClassFile::dumpMethods()
{
  for(unsigned int i=0; i<m_methods.size(); i++) {
    printf(" -%03d ",i);
    m_methods[i]->dump();
  }
}

void ClassFile::dumpInterfaces()
{
  for(u2 i=0;i<m_interfaces.size();i++) {
    ClassFileConstant * c=m_constantPool->getAt(i);
    printf("[%3d] ",i+1);
    c->dump(m_constantPool);
    printf("\n");
  }
}

void ClassFile::dumpConstantPool()
{
  for(u2 i=1;i<m_constantPool->size();i++) {
    printf("#%03d ",i);
    m_constantPool->getAt(i)->dump(m_constantPool,true);
    printf("\n");
  }
}
